{
  lib,
  stdenv,
  buildPackages,
  bc,
  bison,
  flex,
  perl,
  rsync,
  gmp,
  libmpc,
  mpfr,
  openssl,
  cpio,
  elfutils,
  hexdump,
  zstd,
  python3Minimal,
  zlib,
  pahole,
  kmod,
  ubootTools,
  fetchpatch,
  rustc-unwrapped,
  rust-bindgen-unwrapped,
  rustPlatform,
}:

let
  lib_ = lib;
  stdenv_ = stdenv;

  readConfig =
    configfile:
    let
      matchLine =
        line:
        let
          match = lib.match "(CONFIG_[^=]+)=([ym])" line;
        in
        lib.optional (match != null) {
          name = lib.elemAt match 0;
          value = lib.elemAt match 1;
        };
    in
    lib.listToAttrs (lib.concatMap matchLine (lib.splitString "\n" (builtins.readFile configfile)));
in
lib.makeOverridable (
  {
    # The kernel version
    version,
    # The kernel pname (should be set for variants)
    pname ? "linux",
    # Position of the Linux build expression
    pos ? null,
    # Additional kernel make flags
    extraMakeFlags ? [ ],
    # The name of the kernel module directory
    # Needs to be X.Y.Z[-extra], so pad with zeros if needed.
    modDirVersion ? null, # derive from version
    # The kernel source (tarball, git checkout, etc.)
    src,
    # a list of { name=..., patch=..., extraConfig=...} patches
    kernelPatches ? [ ],
    # The kernel .config file
    configfile,
    # Manually specified nixexpr representing the config
    # If unspecified, this will be autodetected from the .config
    config ? lib.optionalAttrs (builtins.isPath configfile || allowImportFromDerivation) (
      readConfig configfile
    ),
    # Custom seed used for CONFIG_GCC_PLUGIN_RANDSTRUCT if enabled. This is
    # automatically extended with extra per-version and per-config values.
    randstructSeed ? "",
    # Extra meta attributes
    extraMeta ? { },

    # for module compatibility
    isZen ? false,
    isLibre ? false,
    isHardened ? false,

    # Whether to utilize the controversial import-from-derivation feature to parse the config
    allowImportFromDerivation ? false,
    # ignored
    features ? null,
    lib ? lib_,
    stdenv ? stdenv_,
  }:

  let
    # Provide defaults. Note that we support `null` so that callers don't need to use optionalAttrs,
    # which can lead to unnecessary strictness and infinite recursions.
    modDirVersion_ = if modDirVersion == null then lib.versions.pad 3 version else modDirVersion;
  in
  let
    # Shadow the un-defaulted parameter; don't want null.
    modDirVersion = modDirVersion_;
    inherit (lib)
      hasAttr
      getAttr
      optional
      optionals
      optionalString
      optionalAttrs
      maintainers
      teams
      platforms
      ;

    drvAttrs =
      config_: kernelConf: kernelPatches: configfile:
      let
        # Folding in `ubootTools` in the default nativeBuildInputs is problematic, as
        # it makes updating U-Boot cumbersome, since it will go above the current
        # threshold of rebuilds
        #
        # To prevent these needless rounds of staging for U-Boot builds, we can
        # limit the inclusion of ubootTools to target platforms where uImage *may*
        # be produced.
        #
        # This command lists those (kernel-named) platforms:
        #     .../linux $ grep -l uImage ./arch/*/Makefile | cut -d'/' -f3 | sort
        #
        # This is still a guesstimation, but since none of our cached platforms
        # coincide in that list, this gives us "perfect" decoupling here.
        linuxPlatformsUsingUImage = [
          "arc"
          "arm"
          "csky"
          "mips"
          "powerpc"
          "sh"
          "sparc"
          "xtensa"
        ];
        needsUbootTools = lib.elem stdenv.hostPlatform.linuxArch linuxPlatformsUsingUImage;

        config =
          let
            attrName = attr: "CONFIG_" + attr;
          in
          {
            isSet = attr: hasAttr (attrName attr) config;

            getValue = attr: if config.isSet attr then getAttr (attrName attr) config else null;

            isYes = attr: (config.getValue attr) == "y";

            isNo = attr: (config.getValue attr) == "n";

            isModule = attr: (config.getValue attr) == "m";

            isEnabled = attr: (config.isModule attr) || (config.isYes attr);

            isDisabled = attr: (!(config.isSet attr)) || (config.isNo attr);
          }
          // config_;

        isModular = config.isYes "MODULES";
        withRust = config.isYes "RUST";

        target = kernelConf.target or "vmlinux";

        buildDTBs = kernelConf.DTB or false;

        # Dependencies that are required to build kernel modules
        moduleBuildDependencies = [
          pahole
          perl
          elfutils
          # module makefiles often run uname commands to find out the kernel version
          (buildPackages.deterministic-uname.override { inherit modDirVersion; })
        ]
        ++ optional (lib.versionAtLeast version "5.13") zstd
        ++ optionals withRust [
          rustc-unwrapped
          rust-bindgen-unwrapped
        ];

      in
      (optionalAttrs isModular {
        outputs = [
          "out"
          "dev"
          "modules"
        ];
      })
      // {
        __structuredAttrs = true;

        passthru = rec {
          inherit
            version
            modDirVersion
            config
            kernelPatches
            configfile
            moduleBuildDependencies
            stdenv
            ;
          inherit
            isZen
            isHardened
            isLibre
            withRust
            ;
          isXen = lib.warn "The isXen attribute is deprecated. All Nixpkgs kernels that support it now have Xen enabled." true;
          baseVersion = lib.head (lib.splitString "-rc" version);
          kernelOlder = lib.versionOlder baseVersion;
          kernelAtLeast = lib.versionAtLeast baseVersion;
        };

        inherit src;

        depsBuildBuild = [ buildPackages.stdenv.cc ];
        nativeBuildInputs = [
          bison
          flex
          perl
          bc
          openssl
          rsync
          gmp
          libmpc
          mpfr
          elfutils
          zstd
          python3Minimal
          kmod
          hexdump
        ]
        ++ optional needsUbootTools ubootTools
        ++ optionals (lib.versionAtLeast version "5.2") [
          cpio
          pahole
          zlib
        ]
        ++ optionals withRust [
          rustc-unwrapped
          rust-bindgen-unwrapped
        ];

        env = {
          RUST_LIB_SRC = lib.optionalString withRust rustPlatform.rustLibSrc;

          # avoid leaking Rust source file names into the final binary, which adds
          # a false dependency on rust-lib-src on targets with uncompressed kernels
          KRUSTFLAGS = lib.optionalString withRust "--remap-path-prefix ${rustPlatform.rustLibSrc}=/";
        };

        patches =
          # kernelPatches can contain config changes and no actual patch
          lib.filter (p: p != null) (map (p: p.patch) kernelPatches)
          # Required for deterministic builds along with some postPatch magic.
          ++ optional (lib.versionOlder version "5.19") ./randstruct-provide-seed.patch
          ++ optional (lib.versionAtLeast version "5.19") ./randstruct-provide-seed-5.19.patch
          # Linux 5.12 marked certain PowerPC-only symbols as GPL, which breaks
          # OpenZFS; this was fixed in Linux 5.19 so we backport the fix
          # https://github.com/openzfs/zfs/pull/13367
          ++
            optional
              (
                lib.versionAtLeast version "5.12" && lib.versionOlder version "5.19" && stdenv.hostPlatform.isPower
              )
              (fetchpatch {
                url = "https://git.kernel.org/pub/scm/linux/kernel/git/powerpc/linux.git/patch/?id=d9e5c3e9e75162f845880535957b7fd0b4637d23";
                hash = "sha256-bBOyJcP6jUvozFJU0SPTOf3cmnTQ6ZZ4PlHjiniHXLU=";
              });

        postPatch = ''
          # Ensure that depmod gets resolved through PATH
          sed -i Makefile -e 's|= /sbin/depmod|= depmod|'

          # Some linux-hardened patches now remove certain files in the scripts directory, so the file may not exist.
          [[ -f scripts/ld-version.sh ]] && patchShebangs scripts/ld-version.sh

          # Set randstruct seed to a deterministic but diversified value. Note:
          # we could have instead patched gen-random-seed.sh to take input from
          # the buildFlags, but that would require also patching the kernel's
          # toplevel Makefile to add a variable export. This would be likely to
          # cause future patch conflicts.
          for file in scripts/gen-randstruct-seed.sh scripts/gcc-plugins/gen-random-seed.sh; do
            if [ -f "$file" ]; then
              substituteInPlace "$file" \
                --replace NIXOS_RANDSTRUCT_SEED \
                $(echo ${randstructSeed}${src} ${placeholder "configfile"} | sha256sum | cut -d ' ' -f 1 | tr -d '\n')
              break
            fi
          done

          patchShebangs scripts

          # also patch arch-specific install scripts
          for i in $(find arch -name install.sh); do
              patchShebangs "$i"
          done

          # unset $src because the build system tries to use it and spams a bunch of warnings
          # see: https://github.com/torvalds/linux/commit/b1992c3772e69a6fd0e3fc81cd4d2820c8b6eca0
          unset src
        '';

        configurePhase = ''
          runHook preConfigure

          mkdir build
          export buildRoot="$(pwd)/build"

          echo "manual-config configurePhase buildRoot=$buildRoot pwd=$PWD"

          if [ -f "$buildRoot/.config" ]; then
            echo "Could not link $buildRoot/.config : file exists"
            exit 1
          fi
          ln -sv ${configfile} $buildRoot/.config

          # reads the existing .config file and prompts the user for options in
          # the current kernel source that are not found in the file.
          make "''${makeFlags[@]}" oldconfig
          runHook postConfigure

          make "''${makeFlags[@]}" prepare
          actualModDirVersion="$(cat $buildRoot/include/config/kernel.release)"
          if [ "$actualModDirVersion" != "${modDirVersion}" ]; then
            echo "Error: modDirVersion ${modDirVersion} specified in the Nix expression is wrong, it should be: $actualModDirVersion"
            exit 1
          fi

          buildFlags+=("KBUILD_BUILD_TIMESTAMP=$(date -u -d @$SOURCE_DATE_EPOCH)")

          cd $buildRoot
        '';

        buildFlags = [
          "KBUILD_BUILD_VERSION=1-NixOS"
          target
          "vmlinux" # for "perf" and things like that
          "scripts_gdb"
        ]
        ++ optional isModular "modules"
        ++ optionals buildDTBs [
          "dtbs"
          "DTC_FLAGS=-@"
        ]
        ++ extraMakeFlags;

        installFlags = [
          "INSTALL_PATH=${placeholder "out"}"
        ]
        ++ (optional isModular "INSTALL_MOD_PATH=${placeholder "modules"}")
        ++ optionals buildDTBs [
          "dtbs_install"
          "INSTALL_DTBS_PATH=${placeholder "out"}/dtbs"
        ];

        preInstall =
          let
            # All we really need to do here is copy the final image and System.map to $out,
            # and use the kernel's modules_install, firmware_install, dtbs_install, etc. targets
            # for the rest. Easy, right?
            #
            # Unfortunately for us, the obvious way of getting the built image path,
            # make -s image_name, does not work correctly, because some architectures
            # (*cough* aarch64 *cough*) change KBUILD_IMAGE on the fly in their install targets,
            # so we end up attempting to install the thing we didn't actually build.
            #
            # Thankfully, there's a way out that doesn't involve just hardcoding everything.
            #
            # The kernel has an install target, which runs a pretty simple shell script
            # (located at scripts/install.sh or arch/$arch/boot/install.sh, depending on
            # which kernel version you're looking at) that tries to do something sensible.
            #
            # (it would be great to hijack this script immediately, as it has all the
            #   information we need passed to it and we don't need it to try and be smart,
            #   but unfortunately, the exact location of the scripts differs between kernel
            #   versions, and they're seemingly not considered to be public API at all)
            #
            # One of the ways it tries to discover what "something sensible" actually is
            # is by delegating to what's supposed to be a user-provided install script
            # located at ~/bin/installkernel.
            #
            # (the other options are:
            #   - a distribution-specific script at /sbin/installkernel,
            #        which we can't really create in the sandbox easily
            #   - an architecture-specific script at arch/$arch/boot/install.sh,
            #        which attempts to guess _something_ and usually guesses very wrong)
            #
            # More specifically, the install script exec's into ~/bin/installkernel, if one
            # exists, with the following arguments:
            #
            # $1: $KERNELRELEASE - full kernel version string
            # $2: $KBUILD_IMAGE - the final image path
            # $3: System.map - path to System.map file, seemingly hardcoded everywhere
            # $4: $INSTALL_PATH - path to the destination directory as specified in installFlags
            #
            # $2 is exactly what we want, so hijack the script and use the knowledge given to it
            # by the makefile overlords for our own nefarious ends.
            #
            # Note that the makefiles specifically look in ~/bin/installkernel, and
            # writeShellScriptBin writes the script to <store path>/bin/installkernel,
            # so HOME needs to be set to just the store path.
            #
            # FIXME: figure out a less roundabout way of doing this.
            installkernel = buildPackages.writeShellScriptBin "installkernel" ''
              cp -av $2 $4
              cp -av $3 $4
            '';
          in
          ''
            installFlags+=("-j$NIX_BUILD_CORES")
            export HOME=${installkernel}
          '';

        # Some image types need special install targets (e.g. uImage is installed with make uinstall on arm)
        installTargets = [
          (kernelConf.installTarget or (
            if target == "uImage" && stdenv.hostPlatform.linuxArch == "arm" then
              "uinstall"
            else if
              (target == "zImage" || target == "Image.gz" || target == "vmlinuz.efi")
              && builtins.elem stdenv.hostPlatform.linuxArch [
                "arm"
                "arm64"
                "parisc"
                "riscv"
              ]
            then
              "zinstall"
            else
              "install"
          )
          )
        ];

        # We remove a bunch of stuff that is symlinked from other places to save space,
        # which trips the broken symlink check. So, just skip it. We'll know if it explodes.
        dontCheckForBrokenSymlinks = true;

        postInstall = optionalString isModular ''
          mkdir -p $dev
          cp vmlinux $dev/

          mkdir -p $dev/lib/modules/${modDirVersion}/build/scripts
          cp -rL ../scripts/gdb/ $dev/lib/modules/${modDirVersion}/build/scripts

          if [ -z "''${dontStrip-}" ]; then
            installFlags+=("INSTALL_MOD_STRIP=1")
          fi
          make modules_install "''${makeFlags[@]}" "''${installFlags[@]}"
          unlink $modules/lib/modules/${modDirVersion}/build

          mkdir -p $dev/lib/modules/${modDirVersion}/{build,source}

          # To save space, exclude a bunch of unneeded stuff when copying.
          (cd .. && rsync --archive --prune-empty-dirs \
              --exclude='/build/' \
              * $dev/lib/modules/${modDirVersion}/source/)

          cd $dev/lib/modules/${modDirVersion}/source

          cp $buildRoot/{.config,Module.symvers} $dev/lib/modules/${modDirVersion}/build
          make modules_prepare "''${makeFlags[@]}" O=$dev/lib/modules/${modDirVersion}/build

          # For reproducibility, removes accidental leftovers from a `cc1` call
          # from a `try-run` call from the Makefile
          rm -f $dev/lib/modules/${modDirVersion}/build/.[0-9]*.d

          # Keep some extra files on some arches (powerpc, aarch64)
          for f in arch/powerpc/lib/crtsavres.o arch/arm64/kernel/ftrace-mod.o; do
            if [ -f "$buildRoot/$f" ]; then
              mkdir -p "$(dirname $dev/lib/modules/${modDirVersion}/build/$f)"
              cp $buildRoot/$f $dev/lib/modules/${modDirVersion}/build/$f
            fi
          done

          # !!! No documentation on how much of the source tree must be kept
          # If/when kernel builds fail due to missing files, you can add
          # them here. Note that we may see packages requiring headers
          # from drivers/ in the future; it adds 50M to keep all of its
          # headers on 3.10 though.

          chmod u+w -R ..
          buildArchDir="$dev/lib/modules/${modDirVersion}/build/arch"

          # Remove unused arches
          for d in $(cd arch/; ls); do
            if [ -d "$buildArchDir/$d" ]; then continue; fi
            if [ -d "$buildArchDir/arm64" ] && [ "$d" = arm ]; then continue; fi
            rm -rf arch/$d
          done

          # Remove all driver-specific code (50M of which is headers)
          rm -fR drivers

          # Keep all headers
          find .  -type f -name '*.h' -print0 | xargs -0 -r chmod u-w

          # Keep linker scripts (they are required for out-of-tree modules on aarch64)
          find .  -type f -name '*.lds' -print0 | xargs -0 -r chmod u-w

          # Keep root and arch-specific Makefiles
          chmod u-w Makefile arch/*/Makefile*

          # Keep whole scripts dir
          chmod u-w -R scripts

          # Delete everything not kept
          find . -type f -perm -u=w -print0 | xargs -0 -r rm

          # Delete empty directories
          find -empty -type d -delete
        '';

        requiredSystemFeatures = [ "big-parallel" ];

        meta = {
          # https://github.com/NixOS/nixpkgs/pull/345534#issuecomment-2391238381
          broken = withRust && lib.versionOlder version "6.12";

          description =
            "The Linux kernel"
            + (
              if kernelPatches == [ ] then
                ""
              else
                " (with patches: " + lib.concatStringsSep ", " (map (x: x.name) kernelPatches) + ")"
            );
          license = lib.licenses.gpl2Only;
          homepage = "https://www.kernel.org/";
          maintainers = [ maintainers.thoughtpolice ];
          teams = [ teams.linux-kernel ];
          platforms = platforms.linux;
          badPlatforms =
            lib.optionals (lib.versionOlder version "4.15") [
              "riscv32-linux"
              "riscv64-linux"
            ]
            ++ lib.optional (lib.versionOlder version "5.19") "loongarch64-linux";
          timeout = 14400; # 4 hours
          identifiers.cpeParts = {
            part = "o";
            vendor = "linux";
            product = "linux_kernel";
            inherit version;
            update = "*";
          };
        }
        // extraMeta;
      };

    commonMakeFlags = import ./common-flags.nix {
      inherit
        lib
        stdenv
        buildPackages
        extraMakeFlags
        ;
    };
  in

  stdenv.mkDerivation (
    builtins.foldl' lib.recursiveUpdate { } [
      (drvAttrs config stdenv.hostPlatform.linux-kernel kernelPatches configfile)
      {
        inherit pname version;

        enableParallelBuilding = true;

        hardeningDisable = [
          "bindnow"
          "format"
          "fortify"
          "stackprotector"
          "pic"
        ];

        makeFlags = [
          "O=$(buildRoot)"

          # We have a `modules` variable in the environment for our
          # split output, but the kernel Makefiles also define their
          # own `modules` variable. Their definition wins, but Make
          # remembers that the variable was originally from the
          # environment and exports it to all the build recipes. This
          # breaks the build with an “Argument list too long” error due
          # to passing the huge list of every module object file in the
          # environment of every process invoked by every build recipe.
          #
          # We use `--eval` here to undefine the inherited environment
          # variable before any Makefiles are read, ensuring that the
          # kernel’s definition creates a new, unexported variable.
          "--eval=undefine modules"
        ]
        ++ commonMakeFlags;

        passthru = { inherit commonMakeFlags; };

        karch = stdenv.hostPlatform.linuxArch;
      }
      (optionalAttrs (pos != null) { inherit pos; })
    ]
  )
)
